/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/builtins_base.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/internal_call_params.h"
#include "plugins/ecmascript/runtime/js_finalization_registry.h"
#include "plugins/ecmascript/runtime/object_factory.h"

namespace ark::ecmascript::builtins {
// 26.2.3.1
JSTaggedValue finalization_registry::Constructor(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), FinalizationRegistry, Constructor);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    // 1.If NewTarget is undefined, throw a TypeError exception
    JSHandle<JSTaggedValue> newTarget = builtins_common::GetNewTarget(argv);
    if (newTarget->IsUndefined()) {
        // throw type error
        THROW_TYPE_ERROR_AND_RETURN(thread, "new target can't be undefined", JSTaggedValue::Exception());
    }
    // 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception.
    if (!builtins_common::GetCallArg(argv, 0)->IsCallable()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "cleanupCallback is not callable", JSTaggedValue::Exception());
    }
    // 3.Let FinalizationRegistry be OrdinaryCreateFromConstructor(
    // NewTarget, "%FinalizationRegistry.prototype%", «[[Realm]], [[CleanupCallback]], [[Cells]]»)
    JSHandle<JSTaggedValue> constructor = builtins_common::GetConstructor(argv);
    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    JSHandle<JSFinalizationRegistry> registry = JSHandle<JSFinalizationRegistry>::Cast(obj);
    // 4. Let fn be the active function object.
    JSHandle<JSFunction> cleanupCallback = JSHandle<JSFunction>::Cast(constructor);
    // 5. Set finalizationRegistry.[[Realm]] to fn.[[Realm]].
    // Skip. Don't know what is it
    // 6. Set finalizationRegistry.[[CleanupCallback]] to HostMakeJobCallback(cleanupCallback).
    registry->SetCleanupCallback(cleanupCallback.GetTaggedValue());
    // 7. Set finalizationRegistry.[[Cells]] to a new empty List.
    JSHandle<TaggedArray> cells = factory->EmptyArray();
    registry->SetCells(cells.GetTaggedValue());

    thread->GetEcmaVM()->RegisterFinalizationRegistry(registry);

    return registry.GetTaggedValue();
}

// 26.2.3.2
JSTaggedValue finalization_registry::proto::Register(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), FinalizationRegistryPrototype, Register);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> self(builtins_common::GetThis(argv));
    // 1. Let finalizationRegistry be the this value.
    if (!self->IsJSFinalizationRegistry()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "'this' is not FinalizationRegistry.", JSTaggedValue::Exception());
    }
    JSHandle<JSFinalizationRegistry> registry = JSHandle<JSFinalizationRegistry>::Cast(self);
    // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
    // 3. If Type(target) is not Object, throw a TypeError exception.
    JSHandle<JSTaggedValue> target = builtins_common::GetCallArg(argv, 0U);
    if (!target->IsECMAObject()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "target is not an object.", JSTaggedValue::Exception());
    }
    // 4. If SameValue(target, heldValue) is true, throw a TypeError exception.
    JSMutableHandle<JSTaggedValue> heldValue(thread, JSTaggedValue::Hole());
    if (argv->GetArgsNumber() >= 2U) {
        heldValue.Update(builtins_common::GetCallArg(argv, 1U));
        if (JSTaggedValue::SameValue(target.GetTaggedValue(), heldValue.GetTaggedValue())) {
            THROW_TYPE_ERROR_AND_RETURN(thread, "target and heldValue are the same.", JSTaggedValue::Exception());
        }
    }
    // 5. If Type(unregisterToken) is not Object, then
    //   a. If unregisterToken is not undefined, throw a TypeError exception.
    //   b. Set unregisterToken to empty.
    JSMutableHandle<JSTaggedValue> unregisterToken(thread, builtins_common::GetCallArg(argv, 2U));
    if (!unregisterToken->IsECMAObject()) {
        if (!unregisterToken->IsUndefined()) {
            THROW_TYPE_ERROR_AND_RETURN(thread, "unregisterToken must be an object.", JSTaggedValue::Exception());
        }
        unregisterToken.Update(JSTaggedValue::Hole());
    }
    // 6. Let cell be the Record { [[WeakRefTarget]]: target, [[HeldValue]]:
    // heldValue, [[UnregisterToken]]: unregisterToken }.
    // 7. Append cell to finalizationRegistry.[[Cells]].
    JSFinalizationRegistry::Register(thread, registry, target, heldValue, unregisterToken);
    // 8. Return undefined.
    return JSTaggedValue::Undefined();
}

// 26.2.3.3
JSTaggedValue finalization_registry::proto::Unregister(EcmaRuntimeCallInfo *argv)
{
    ASSERT(argv);
    BUILTINS_API_TRACE(argv->GetThread(), FinalizationRegistryPrototype, Unregister);
    JSThread *thread = argv->GetThread();
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    JSHandle<JSTaggedValue> self(builtins_common::GetThis(argv));
    // 1. Let finalizationRegistry be the this value.
    if (!self->IsJSFinalizationRegistry()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "'this' is not FinalizationRegistry.", JSTaggedValue::Exception());
    }
    JSHandle<JSFinalizationRegistry> registry = JSHandle<JSFinalizationRegistry>::Cast(self);
    // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
    // 3. If Type(unregisterToken) is not Object, throw a TypeError exception.
    JSHandle<JSTaggedValue> unregisterToken = builtins_common::GetCallArg(argv, 0U);
    if (!unregisterToken->IsECMAObject()) {
        THROW_TYPE_ERROR_AND_RETURN(thread, "unregisterToken must be an object.", JSTaggedValue::Exception());
    }
    // 4. Let removed be false.
    // 5. For each Record { [[WeakRefTarget]], [[HeldValue]], [[UnregisterToken]] } cell of
    // finalizationRegistry.[[Cells]], do
    bool removed = JSFinalizationRegistry::Unregister(thread, registry, unregisterToken);
    // 8. Return removed.
    return JSTaggedValue(removed);
}
}  // namespace ark::ecmascript::builtins
